#include <RenderSubSystem/cvIMRenderer/IMCameraObject.h>
#include <Math/Matrix4x4.hpp>
#include <gl/GLFW.h>
#include <Exceptions/OpenGLException.h>

namespace cvImmediateModeRenderer
{
	IMCameraObject *  IMCameraObject::create()
	{
		return ::new IMCameraObject;
	}

	IMCameraObject::IMCameraObject() :
		fn_setup( &IMCameraObject::setup_screencoordinates )
	{
		m_viewport.l = 0;
		m_viewport.t = 0;
		glfwGetWindowSize( &m_viewport.r, &m_viewport.b );
	}

	IMCameraObject * IMCameraObject::copy( cvMath::bitfield_t flags )
	{
		// TODO: not complete..
		IMCameraObject * obj = IMCameraObject::create();
		update_linear();
		obj->m_viewport = m_viewport;
		obj->m_zoom = m_zoom;
		return obj;
	}

	void IMCameraObject::destroy()	{ ::delete this; }
	IMCameraObject::~IMCameraObject()	{}

	void IMCameraObject::useScreenCoordinates()
	{
		fn_setup = &IMCameraObject::setup_screencoordinates;
		m_offset.x = 0.5f;
		m_offset.y = 0.5f;
	}
	void IMCameraObject::useWorldCoordinates()
	{
		fn_setup = &IMCameraObject::setup_worldcoordinates;
		m_offset.x = 0.0f;
		m_offset.y = 0.0f;
	}
	void IMCameraObject::setup()	{ ( this->*fn_setup )(); }

	void IMCameraObject::setup_screencoordinates()
	{
		using namespace cvMath;
		Vec2f wh( rectWidth( m_viewport ), rectHeight( m_viewport ) );
		Mat4x4f mat1, mat2;

		// SETUP VIEWPORT
		glViewport( m_viewport.l, m_viewport.t, wh.x, wh.y );
		CRITICAL_CHECK_GLERRORS( "glViewport()" );

		// SETUP PROJECTION MATRIX
		identity( mat1 );
		mat1 *= ortho_screen2d(( float )wh.x, ( float )wh.y );
		glMatrixMode( GL_PROJECTION );
		glLoadMatrixf(( GLfloat* )&mat1.mat );
		m_bbtransfrom = mat1;

		// SETUP MODELVIEW MATRIX
		identity( mat1 );
		mat2 = mat1;
		translationXY( mat1, m_offset * -wh );
		mat1 *= scaleXY( m_zoom.x, m_zoom.y );
		translationXY( mat2, position() + ( m_offset * wh ) );
		mat1 *= rotationZ( mat2, angle() );
		glMatrixMode( GL_MODELVIEW );
		glLoadMatrixf(( GLfloat* )&mat1.mat );
		m_bbtransfrom *= mat1;
	}

	void IMCameraObject::setup_worldcoordinates()
	{
		using namespace cvMath;
		Vec2f wh( rectWidth( m_viewport ), rectHeight( m_viewport ) );
		Mat4x4f mat1, mat2;

		// SETUP VIEWPORT
		glViewport( m_viewport.l, m_viewport.t, wh.x, wh.y );
		CRITICAL_CHECK_GLERRORS( "glViewport()" );

		// SETUP PROJECTION MATRIX
		identity( mat1 );
		mat1 *= ortho_world2d( wh.x, wh.y );
		glMatrixMode( GL_PROJECTION );
		glLoadMatrixf( (GLfloat*)&mat1.mat );
		m_bbtransfrom = mat1;
		// SETUP MODELVIEW MATRIX
		identity( mat1 );
		mat2 = mat1;
		translationXY( mat1, m_offset * -(wh/2.0f) );	//OFFSET
		mat1 *= scaleXY( m_zoom.x, m_zoom.y );		//ZOOM
		translationXY( mat2, position() + ( m_offset * (wh/2.0f) ) );	//TRANSLATE
		mat1 *= rotationZ( mat2, angle() );		// ROTATE
		glMatrixMode( GL_MODELVIEW );
		glLoadMatrixf( (GLfloat*)&mat1.mat );
		m_bbtransfrom *= mat1;
	}

	cvMath::Rectf IMCameraObject::getBoundingBox()
	{
		using namespace cvMath;
		Rectf result;
		Vec2f p[0];
		Vec2f absoffset;
		absoffset.x = m_offset.x * rectWidth(m_viewport);
		absoffset.y = m_offset.y * rectHeight(m_viewport);
		rectCorners(offsetRect<float>(m_viewport, absoffset), p, p+1, p+2, p+3);
		for(size_t i = 0; i < 4; ++i)
		{
			p[i] = cvMath::tranform(p[i], m_bbtransfrom);
			result = expandRect(result, p[i]);
		}
		return result;
	}


};
